﻿using Dorado.Extensions;
using Dorado.Platform.Infrastructure;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;

namespace Dorado.Platform.Plugins
{
    [DataContract]
    public class PluginDescriptor : IComparable<PluginDescriptor>
    {
        private string _resourceRootKey;
        private string _brandImageFileName;

        public PluginDescriptor()
        {
            this.Version = new Version("1.0");
            this.MinAppVersion = PlatformVersion.Version;
        }

        // Unit tests
        public PluginDescriptor(Assembly referencedAssembly, FileInfo originalAssemblyFile, Type pluginType)
            : this()
        {
            this.ReferencedAssembly = referencedAssembly;
            this.OriginalAssemblyFile = originalAssemblyFile;
            this.PluginType = pluginType;
        }

        /// <summary>
        /// Plugin file name
        /// </summary>
        public string PluginFileName { get; set; }

        /// <summary>
        /// The physical path of the runtime plugin
        /// </summary>
		public string PhysicalPath { get; set; }

        /// <summary>
        /// Gets the file name of the brand image (without path)
        /// or an empty string if no image is specified
        /// </summary>
        public string BrandImageFileName
        {
            get
            {
                if (_brandImageFileName == null)
                {
                    // "null" means we haven't checked yet!
                    var filesToCheck = new[] { "branding.png", "branding.gif", "branding.jpg", "branding.jpeg" };
                    var dir = this.PhysicalPath;
                    foreach (var file in filesToCheck)
                    {
                        if (File.Exists(Path.Combine(dir, "Content", file)))
                        {
                            _brandImageFileName = file;
                            break;
                        }
                    }

                    // indicate that we have checked already (although no file was found)
                    if (_brandImageFileName == null)
                        _brandImageFileName = String.Empty;
                }

                return _brandImageFileName;
            }
        }

        /// <summary>
        /// Plugin type
        /// </summary>
        public Type PluginType { get; set; }

        /// <summary>
        /// The assembly that has been shadow copied that is active in the application
        /// </summary>
        public Assembly ReferencedAssembly { get; internal set; }

        /// <summary>
        /// The original assembly file that a shadow copy was made from it
        /// </summary>
        public FileInfo OriginalAssemblyFile { get; internal set; }

        /// <summary>
        /// Gets or sets the plugin group
        /// </summary>
		[DataMember]
        public string Group { get; internal set; }

        public bool IsInKnownGroup
        {
            get
            {
                return PluginFileParser.KnownGroups.Contains(this.Group, StringComparer.OrdinalIgnoreCase);
            }
        }

        /// <summary>
        /// Gets or sets the friendly name
        /// </summary>
		[DataMember]
        public string FriendlyName { get; set; }

        /// <summary>
        /// Gets the folder name
        /// </summary>
        [DataMember]
        public string FolderName { get; internal set; }

        /// <summary>
        /// Gets or sets the system name
        /// </summary>
		[DataMember]
        public string SystemName { get; set; }

        /// <summary>
        /// Gets the plugin description
        /// </summary>
		[DataMember]
        public string Description { get; set; }

        /// <summary>
        /// Gets or sets the version
        /// </summary>
		[DataMember]
        public Version Version { get; set; }

        /// <summary>
        /// Gets or sets the minimum supported app version
        /// </summary>
		[DataMember]
        public Version MinAppVersion { get; set; }

        /// <summary>
        /// Gets or sets the author
        /// </summary>
		[DataMember]
        public string Author { get; set; }

        /// <summary>
        /// Gets or sets the project/marketplace url
        /// </summary>
        [DataMember]
        public string Url { get; set; }

        /// <summary>
        /// Gets or sets the display order
        /// </summary>
		[DataMember]
        public int DisplayOrder { get; set; }

        /// <summary>
        /// Gets or sets the value indicating whether plugin is installed
        /// </summary>
		[DataMember]
        public bool Installed { get; set; }

        /// <summary>
        /// Gets or sets the value indicating whether the plugin is configurable
        /// </summary>
        /// <remarks>
        /// A plugin is configurable when it implements the <see cref="IConfigurable"/> interface
        /// </remarks>
        [DataMember]
        public bool IsConfigurable { get; set; }

        /// <summary>
        /// Gets or sets the root key of string resources.
        /// </summary>
        /// <remarks>Tries to get it from first entry of resource XML file if not specified. In that case the first resource name should not contain a dot if it's not part of the root key.
        /// Otherwise you get the wrong root key.</remarks>
        public string ResourceRootKey
        {
            get
            {
                if (_resourceRootKey == null)
                {
                    _resourceRootKey = "";
                    try
                    {
                        // try to get root-key from first entry of XML file
                        string localizationDir = Path.Combine(PhysicalPath, "Localization");

                        if (System.IO.Directory.Exists(localizationDir))
                        {
                            string filePath = System.IO.Directory.EnumerateFiles(localizationDir, "*.xml").FirstOrDefault();
                            if (filePath.HasValue())
                            {
                                XmlDocument doc = new XmlDocument();
                                doc.Load(filePath);
                                var node = doc.SelectSingleNode(@"//Language/LocaleResource");
                                if (node != null)
                                {
                                    string key = node.Attributes["Name"].InnerText;
                                    if (key.HasValue() && key.Contains('.'))
                                        _resourceRootKey = key.Substring(0, key.LastIndexOf('.'));
                                }
                            }
                        }
                    }
                    catch (Exception exc)
                    {
                        exc.Dump();
                    }
                }
                return _resourceRootKey;
            }
            set
            {
                _resourceRootKey = value;
            }
        }

        public T Instance<T>() where T : class, IPlugin
        {
            object instance;
            if (!DoradoContext.Instance.ContainerManager.TryResolve(PluginType, null, out instance))
            {
                //not resolved
                instance = DoradoContext.Instance.ContainerManager.ResolveUnregistered(PluginType);
            }
            var typedInstance = instance as T;
            if (typedInstance != null)
                typedInstance.PluginDescriptor = this;
            return typedInstance;
        }

        public IPlugin Instance()
        {
            return Instance<IPlugin>();
        }

        [SuppressMessage("ReSharper", "StringCompareToIsCultureSpecific")]
        public int CompareTo(PluginDescriptor other)
        {
            if (DisplayOrder != other.DisplayOrder)
                return DisplayOrder.CompareTo(other.DisplayOrder);
            else if (FriendlyName != null)
                return FriendlyName.CompareTo(other.FriendlyName);
            return 0;
        }

        public string GetSettingKey(string name)
        {
            return "PluginSetting.{0}.{1}".FormatWith(SystemName, name);
        }

        public override string ToString()
        {
            return FriendlyName;
        }

        public override bool Equals(object obj)
        {
            var other = obj as PluginDescriptor;
            return other != null &&
                SystemName != null &&
                SystemName.Equals(other.SystemName);
        }

        public override int GetHashCode()
        {
            return SystemName.GetHashCode();
        }
    }
}